﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;

[assembly: WebResource("AccessKeyHighlighterControl.AccessKeyHighlighter.js", "text/javascript")]
[assembly: WebResource("AccessKeyHighlighterControl.AccessKeyHighlighter.css", "text/css", PerformSubstitution = true)]
[assembly: WebResource("AccessKeyHighlighterControl.popup-back.gif", "image/gif")]

namespace AccessKeyHighlighterControl
{
    /// <summary>
    /// Represents the different modes that access keys can be highlighted on a web form
    /// </summary>
    public enum AccessKeyHighlightMode
    {
        ToggleLabelClass = 0,
        DisplayPopup = 1
    }

    /// <summary>
    /// Highlights marked up access keys on a web form when the user holds down the browser's access key shortcut key.
    /// </summary>
    [ToolboxData("<{0}:AccessKeyHighlighter runat=server></{0}:AccessKeyHighlighter>")]
    [DefaultProperty("HighlightMode")]
    public class AccessKeyHighlighter : Control, IScriptControl
    {
        #region << Properties >>

        private const string HIGHLIGHT_CSS_CLASS_DEFAULT = "AccessKeyHighlighter__highlighted";
        private string _highlightCssClass;
        /// <summary>
        /// Gets or sets the CSS class to use for the highlighted access keys.
        /// </summary>
        [DefaultValue(HIGHLIGHT_CSS_CLASS_DEFAULT)]
        [Category("Appearance")]
        public string HighlightCssClass
        {
            get { return (EnableViewState ? (ViewState["HighlightCssClass"] ?? HIGHLIGHT_CSS_CLASS_DEFAULT).ToString() : _highlightCssClass); }
            set { if (EnableViewState) ViewState["HighlightCssClass"] = value; else _highlightCssClass = value; }
        }

        private const string POPUP_CSS_CLASS_DEFAULT = "AccessKeyHighlighter__popup";
        private string _popupCssClass;
        /// <summary>
        /// Gets or sets the CSS class to use for the access key popups.
        /// </summary>
        [DefaultValue(POPUP_CSS_CLASS_DEFAULT)]
        [Category("Appearance")]
        public string PopupCssClass
        {
            get { return (EnableViewState ? (ViewState["PopupCssClass"] ?? POPUP_CSS_CLASS_DEFAULT).ToString() : _popupCssClass); }
            set { if (EnableViewState) ViewState["PopupCssClass"] = value; else _popupCssClass = value; }
        }

        private HtmlTextWriterTag _accessKeyTag = HtmlTextWriterTag.Em;
        /// <summary>
        /// Gets or sets the tag used to mark up the access key in the form's labels.
        /// Default tag used is &lt;em&gt;&lt;/em&gt;.
        /// </summary>
        [DefaultValue(HtmlTextWriterTag.Em)]
        [Category("Behavior")]
        public HtmlTextWriterTag AccessKeyTag
        {
            get { return (EnableViewState ? (HtmlTextWriterTag)(ViewState["AccessKeyTag"] ?? HtmlTextWriterTag.Em) : _accessKeyTag); }
            set { if (EnableViewState) ViewState["AccessKeyTag"] = value; else _accessKeyTag = value; }
        }

        private AccessKeyHighlightMode _highlightMode;
        /// <summary>
        /// Gets or sets the access key highlight mode.
        /// The default mode is DisplayPopup.
        /// </summary>
        [DefaultValue(AccessKeyHighlightMode.DisplayPopup)]
        [Category("Appearance")]
        public AccessKeyHighlightMode HighlightMode
        {
            get { return (EnableViewState ? (AccessKeyHighlightMode)Enum.Parse(typeof(AccessKeyHighlightMode), (ViewState["HighlightMode"] ?? AccessKeyHighlightMode.DisplayPopup).ToString()) : _highlightMode); }
            set { if (EnableViewState) ViewState["HighlightMode"] = value; else _highlightMode = value; }
        }

        private int _popupLabelOffsetX = -20;
        /// <summary>
        /// Gets or sets the offset in the X axis of the popup for labels.
        /// Default is -20.
        /// </summary>
        [DefaultValue(-20)]
        [Category("Appearance")]
        public int PopupLabelOffsetX
        {
            get { return (EnableViewState ? (int)(ViewState["PopupLabelOffsetX"] ?? -20) : _popupLabelOffsetX); }
            set { if (EnableViewState) ViewState["PopupLabelOffsetX"] = value; else _popupLabelOffsetX = value; }
        }

        private int _popupLabelOffsetY = 2;
        /// <summary>
        /// Gets or sets the offset in the Y axis of the popup for labels.
        /// Default is +2.
        /// </summary>
        [DefaultValue(2)]
        [Category("Appearance")]
        public int PopupLabelOffsetY
        {
            get { return (EnableViewState ? (int)(ViewState["PopupLabelOffsetY"] ?? 2) : _popupLabelOffsetY); }
            set { if (EnableViewState) ViewState["PopupLabelOffsetY"] = value; else _popupLabelOffsetY = value; }
        }

        private int _popupButtonOffsetX = -3;
        /// <summary>
        /// Gets or sets the offset in the X axis of the popup for buttons.
        /// Default is -3.
        /// </summary>
        [DefaultValue(-3)]
        [Category("Appearance")]
        public int PopupButtonOffsetX
        {
            get { return (EnableViewState ? (int)(ViewState["PopupButtonOffsetX"] ?? -3) : _popupButtonOffsetX); }
            set { if (EnableViewState) ViewState["PopupButtonOffsetX"] = value; else _popupButtonOffsetX = value; }
        }

        private int _popupButtonOffsetY = -3;
        /// <summary>
        /// Gets or sets the offset in the Y axis of the popup for buttons.
        /// Default is -3.
        /// </summary>
        [DefaultValue(-3)]
        [Category("Appearance")]
        public int PopupButtonOffsetY
        {
            get { return (EnableViewState ? (int)(ViewState["PopupButtonOffsetY"] ?? -3) : _popupButtonOffsetY); }
            set { if (EnableViewState) ViewState["PopupButtonOffsetY"] = value; else _popupButtonOffsetY = value; }
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="AccessKeyHighlighter"/> class.
        /// </summary>
        public AccessKeyHighlighter()
            : base()
        { }

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.Load"/> event.
        /// </summary>
        /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            // Add stylesheet link to page
            string cssLink = Page.ClientScript.GetWebResourceUrl(typeof(AccessKeyHighlighter), "AccessKeyHighlighterControl.AccessKeyHighlighter.css");
            bool hasLink = (this.Page.Header.Controls.OfType<HtmlLink>().Count(l => l.Href == cssLink) >= 1);

            if (!hasLink)
            {
                HtmlLink link = new HtmlLink();
                link.Href = cssLink;
                link.Attributes["type"] = "text/css";
                link.Attributes["rel"] = "stylesheet";
                this.Page.Header.Controls.Add(link);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param>
        protected override void OnPreRender(EventArgs e)
        {
#if DEBUG
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
#endif
            if (!this.DesignMode)
            {
                // Test for ScriptManager and register if it exists
                ScriptManager sm = ScriptManager.GetCurrent(Page);

                if (sm == null)
                    throw new HttpException("A ScriptManager control must exist on the current page.");

                sm.RegisterScriptControl<AccessKeyHighlighter>(this);
            }

            base.OnPreRender(e);

            // Add the access key markup
            Helpers.InjectAccessKeyMarkup(this);

#if DEBUG
            sw.Stop();
            System.Diagnostics.Debug.WriteLine(String.Format("AccessKeyHighlighter.OnPreRender() took {0}", sw.Elapsed.ToString()));
#endif
        }

        /// <summary>
        /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param>
        protected override void Render(HtmlTextWriter writer)
        {
            ScriptManager sm = ScriptManager.GetCurrent(Page);

            if (!this.DesignMode)
                sm.RegisterScriptDescriptors(this);

            base.Render(writer);
        }

        #region << IScriptControl Members >>

        /// <summary>
        /// Gets a collection of script descriptors that represent ECMAScript (JavaScript) client components.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerable"/> collection of <see cref="T:System.Web.UI.ScriptDescriptor"/> objects.
        /// </returns>
        IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
        {
            ScriptComponentDescriptor descriptor = new ScriptComponentDescriptor("AccessKeyHighlighterControl.AccessKeyHighlighter");
            descriptor.AddProperty("highlightCssClass", this.HighlightCssClass);
            descriptor.AddProperty("popupCssClass", this.PopupCssClass);
            descriptor.AddProperty("accessKeyTag", this.AccessKeyTag.ToString().ToUpper());
            descriptor.AddProperty("highlightMode", this.HighlightMode);
            descriptor.AddProperty("popupLabelOffsetX", this.PopupLabelOffsetX);
            descriptor.AddProperty("popupLabelOffsetY", this.PopupLabelOffsetY);
            descriptor.AddProperty("popupButtonOffsetX", this.PopupButtonOffsetX);
            descriptor.AddProperty("popupButtonOffsetY", this.PopupButtonOffsetY);
            yield return descriptor;
        }

        /// <summary>
        /// Gets a collection of <see cref="T:System.Web.UI.ScriptReference"/> objects that define script resources that the control requires.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerable"/> collection of <see cref="T:System.Web.UI.ScriptReference"/> objects.
        /// </returns>
        IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
        {
            yield return new ScriptReference("AccessKeyHighlighterControl.AccessKeyHighlighter.js", this.GetType().Assembly.FullName);
        }

        #endregion
    }
}